{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 函数\n",
    "函数是组织好的，可重复使用的，用来实现单一，或相关联功能的代码段。\n",
    "\n",
    "函数能提高应用的模块性，和代码的重复利用率。你已经知道Python提供了许多内建函数，比如print()。但你也可以自己创建函数，这被叫做用户自定义函数。\n",
    "\n",
    "#### 定义一个函数\n",
    "你可以定义一个由自己想要功能的函数，以下是简单的规则：\n",
    "\n",
    "函数代码块以 def 关键词开头，后接函数标识符名称和圆括号 ()。\n",
    "任何传入参数和自变量必须放在圆括号中间，圆括号之间可以用于定义参数。\n",
    "函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。\n",
    "函数内容以冒号起始，并且缩进。\n",
    "return [表达式] 结束函数，选择性地返回一个值给调用方。不带表达式的return相当于返回 None。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 实例\n",
    "def hello():\n",
    "    print(\"Hello World!\")\n",
    "\n",
    "def my_abs(x):\n",
    "    if x >= 0:\n",
    "        return x\n",
    "    else:\n",
    "        return -x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 调用函数\n",
    "hello()\n",
    "\n",
    "result = my_abs(-5)\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 参数传递\n",
    "\n",
    "可更改(mutable)与不可更改(immutable)对象\n",
    "在 python 中，strings, tuples, 和 numbers 是不可更改的对象，而 list,dict 等则是可以修改的对象。\n",
    "\n",
    "不可变类型：变量赋值 a=5 后再赋值 a=10，这里实际是新生成一个 int 值对象 10，再让 a 指向它，而 5 被丢弃，不是改变a的值，相当于新生成了a。\n",
    "\n",
    "可变类型：变量赋值 la=[1,2,3,4] 后再赋值 la[2]=5 则是将 list la 的第三个元素值更改，本身la没有动，只是其内部的一部分值被修改了。\n",
    "\n",
    "python 函数的参数传递：\n",
    "\n",
    "不可变类型：类似 c++ 的值传递，如 整数、字符串、元组。如fun（a），传递的只是a的值，没有影响a对象本身。比如在 fun（a）内部修改 a 的值，只是修改另一个复制的对象，不会影响 a 本身。\n",
    "\n",
    "可变类型：类似 c++ 的引用传递，如 列表，字典。如 fun（la），则是将 la 真正的传过去，修改后fun外部的la也会受影响\n",
    "\n",
    "python 中一切都是对象，严格意义我们不能说值传递还是引用传递，我们应该说传不可变对象和传可变对象"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 传不可变对象实例，类似Ｃ语音里面的值传递\n",
    "def ChangeInt(a):\n",
    "    print('函数内取值', a)\n",
    "    a = 10\n",
    "    print('Has changed', a)\n",
    "\n",
    "a = 2\n",
    "ChangeInt(a)\n",
    "print('函数外取值', a) # 结果是 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 传可变对象实例，类似Ｃ语音里面的传内存地址，引用传递\n",
    "def changeme(mylist):\n",
    "   # \"修改传入的列表\"\n",
    "   mylist.append([1,2,3,4])\n",
    "   print(\"函数内取值: \", mylist)\n",
    " \n",
    "# 调用changeme函数\n",
    "mylist = [10,20,30]\n",
    "changeme(mylist)\n",
    "print(\"函数外取值: \", mylist)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "a = 10\n",
    "print(id(a))\n",
    "b = a\n",
    "print(id(b))\n",
    "b = a + 10\n",
    "print(id(b))\n",
    "\n",
    "l = [1,2,3]\n",
    "print(id(l))\n",
    "l.append(4)\n",
    "print(id(l))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 参数\n",
    "以下是调用函数时可使用的正式参数类型：\n",
    "\n",
    "1. 必备参数  -- 以上都是必备参数\n",
    "2. 关键字参数\n",
    "3. 默认参数\n",
    "4. 不定长参数"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 关键字参数\n",
    "关键字参数和函数调用关系紧密，函数调用使用关键字参数来确定传入的参数值。\n",
    "\n",
    "使用关键字参数允许函数调用时参数的顺序与声明时不一致，因为 Python 解释器能够用参数名匹配参数值。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#函数说明\n",
    "def printinfo( name, age ):\n",
    "    # \"打印任何传入的字符串\"\n",
    "    print(\"Name: \", name)\n",
    "    print(\"Age \", age)\n",
    "\n",
    "# 调用printinfo函数--关键字参数\n",
    "printinfo(age=50, name=\"miki\")\n",
    "# 必备参数\n",
    "printinfo(\"Tom\", 44)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 缺省参数\n",
    "调用函数时，缺省参数的值如果没有传入，则被认为是默认值。下例会打印默认的age，如果age没有被传入："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#可写函数说明\n",
    "def printinfo2( name, age = 35 ):\n",
    "    # \"打印任何传入的字符串\"\n",
    "    print(\"Name: \", name)\n",
    "    print(\"Age \", age)\n",
    "\n",
    "    # 缺省参数\n",
    "printinfo2(\"Mery\")  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 不定长参数\n",
    "你可能需要一个函数能处理比当初声明时更多的参数。这些参数叫做不定长参数，和上述2种参数不同，声明时不会命名。基本语法如下："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 函数说明\n",
    "def printinfo3(arg1, *vartuple ):\n",
    "    print(\"arg1:\", arg1)\n",
    "    for var in vartuple:\n",
    "        print(\"不定长参数\", var)\n",
    " \n",
    "# 调用printinfo 函数\n",
    "printinfo3(10);\n",
    "print('-------------------')\n",
    "printinfo3(70, 60, 50);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### return 语句\n",
    "return语句[表达式]退出函数，选择性地向调用方返回一个表达式。不带参数值的return语句返回None。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 可写函数说明\n",
    "def sum( arg1, arg2 ):\n",
    "    # 返回2个参数的和.\"\n",
    "    total = arg1 + arg2\n",
    "    print(\"函数内 : \", total)\n",
    "    return total\n",
    " \n",
    "# 调用sum函数\n",
    "total = sum( 10, 20 )\n",
    "print(total)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 变量作用域\n",
    "Python 中，程序的变量并不是在哪个位置都可以访问的，访问权限决定于这个变量是在哪里赋值的。\n",
    "\n",
    "变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。Python的作用域一共有4种，分别是：\n",
    "\n",
    "1. L （Local） 局部作用域\n",
    "2. E （Enclosing） 闭包函数外的函数中\n",
    "3. G （Global） 全局作用域\n",
    "4. B （Built-in） 内建作用域\n",
    "\n",
    "以 L –> E –> G –>B 的规则查找，即：在局部找不到，便会去局部外的局部找（例如闭包），再找不到就会去全局找，再者去内建中找。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### L(Local)局部变量\n",
    "\n",
    "包含在def关键字定义的语句块中，即在函数中定义的变量。每当函数被调用时都会创建一个新的局部作用域。Python中也有递归，即自己调用自己，每次调用都会创建一个新的局部命名空间。在函数内部的变量声明，除非特别的声明为全局变量，否则均默认为局部变量。有些情况需要在函数内部定义全局变量，这时可以使用global关键字来声明变量的作用域为全局。局部变量域就像一个 栈，仅仅是暂时的存在，依赖创建该局部作用域的函数是否处于活动的状态。所以，一般建议尽量少定义全局变量，因为全局变量在模块文件运行的过程中会一直存在，占用内存空间。\n",
    "\n",
    "#### E(enclosing)闭包函数外的函数中\n",
    "\n",
    "E也包含在def关键字中，E和L是相对的，E相对于更上层的函数而言也是L。与L的区别在于，对一个函数而言，L是定义在此函数内部的局部作用域，而E是定义在此函数的上一层父级函数的局部作用域。主要是为了实现Python的闭包，而增加的实现。\n",
    "\n",
    "#### G(global)全局作用域\n",
    "\n",
    "即在模块层次中定义的变量，每一个模块都是一个全局作用域。也就是说，在模块文件顶层声明的变量具有全局作用域，从外部开来，模块的全局变量就是一个模块对象的属性。\n",
    "注意：全局作用域的作用范围仅限于单个模块文件内\n",
    "\n",
    "#### B(built-in)内建作用域\n",
    "系统内固定模块里定义的变量，如预定义在builtin 模块内的变量。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "print(int(2.9))  # 内建作用域\n",
    " \n",
    "count = 0  # 全局作用域\n",
    "def outer():\n",
    "    count = 1  # 闭包函数外的函数中\n",
    "    print('outer 函数里面', count)\n",
    "    def inner():\n",
    "        count = 2  # 局部作用域\n",
    "        print('inner 函数里面', count)\n",
    "    \n",
    "    inner()\n",
    "    print('运行inner函数之后', count)\n",
    "outer()\n",
    "print('全局', count)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Python 中只有模块（module），类（class）以及函数（def、lambda）才会引入新的作用域，其它的代码块（如 if/elif/else/、try/except、for/while等）是不会引入新的作用域的，也就是说这些语句内定义的变量，外部也可以访问，如下代码：\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "if True:\n",
    "    a = 0\n",
    "\n",
    "# 虽然没有定义a，但还是能访问\n",
    "print(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def fun_a():\n",
    "    fun_var_a = 5\n",
    "    print(\"在fun_a里面：\", fun_var_a)\n",
    "fun_a()\n",
    "print(func_var_a)  # name 'func_a' is not defined"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### global 和 nonlocal关键字\n",
    "当内部作用域想修改外部作用域的变量时，就要用到global和nonlocal关键字了。\n",
    "\n",
    "以下实例修改全局变量 num："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "num = 1\n",
    "def fun1():\n",
    "    global num  # 需要使用 global 关键字声明\n",
    "    print('fun1函数里面', num) \n",
    "    num = 123\n",
    "    print('fun1里面，修改值之后',num)\n",
    "fun1()\n",
    "print(u'运行函数后',num)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# python3 语法\n",
    "num = 1\n",
    "def outer():\n",
    "    num = 10\n",
    "    def inner():\n",
    "        nonlocal num   # nonlocal关键字声明\n",
    "        #global num\n",
    "        print(\"inner_1:\", num)\n",
    "        num = 100\n",
    "        print(\"inner_2:\", num)\n",
    "    inner()\n",
    "    print(\"outer:\", num)\n",
    "outer()\n",
    "print(\"外面的\", num)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 错误示范\n",
    "a = 10\n",
    "def test():\n",
    "    a = a + 1\n",
    "    print(a)\n",
    "test()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 推荐做法，传入参数\n",
    "a = 10\n",
    "def test(a):\n",
    "    a = a + 1\n",
    "    print(a)\n",
    "test(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 不推荐做法\n",
    "a = 10\n",
    "def test():\n",
    "    global a \n",
    "    a = a + 1\n",
    "    print(a)\n",
    "test()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 练习"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 请定义一个 square_of_sum 函数，它接受一个list，返回list中每个元素平方的和\n",
    "\n",
    "def square_of_sum(L):\n",
    "    ???\n",
    "\n",
    "print(square_of_sum([1, 2, 3, 4, 5]))\n",
    "print(square_of_sum([-5, 0, 5, 15, 25]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 请定义一个 greet() 函数，它包含一个默认参数，\n",
    "# 如果没有传入，打印 'Hello, world.'，如果传入，打印 'Hello, xxx.'\n",
    "\n",
    "def greet(???):\n",
    "    print(???)\n",
    "\n",
    "greet()\n",
    "greet('Bart')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 扩展知识- 函数式编程\n",
    "\n",
    "#### 传入函数\n",
    "既然变量可以指向函数，函数的参数能接收变量，那么一个函数就可以接收另一个函数作为参数，这种函数就称之为高阶函数。\n",
    "\n",
    "一个最简单的高阶函数：\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def add(x, y, f):\n",
    "    return f(x) + f(y)\n",
    "\n",
    "print(add(-5, 6, abs))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Map 函数\n",
    "\n",
    "map()函数接收两个参数，一个是函数，一个是列表，map将传入的函数依次作用到序列的每个元素，并把结果作为新的列表返回\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def f(x):\n",
    "    return x * x\n",
    "\n",
    "r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])\n",
    "print('python2 和 python3 有些不同，在3里面，r是一个迭代器', r)\n",
    "# 这是错误的，print(r[0])，迭代器没有序号，不是队列，每次使用next函数只会返回一个值\n",
    "# print(next(r)) 可以获取下一个值\n",
    "# list(r) # 这样可以把迭代器转换成队列\n",
    "\n",
    "# for循环可以自动判断迭代器的结束，可以用for来遍历\n",
    "for i in r:\n",
    "    print(i)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 同等效果\n",
    "L = []\n",
    "for n in [1, 2, 3, 4, 5, 6, 7, 8, 9]:\n",
    "    L.append(f(n))\n",
    "print(L)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Reduce \n",
    "reduce把一个函数作用在一个序列[x1, x2, x3, ...]上，这个函数必须接收两个参数，reduce把结果继续和序列的下一个元素做累积计算，其效果就是："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from functools import reduce  # python2 不需要\n",
    "\n",
    "def fn(x, y):\n",
    "    return x + y\n",
    "\n",
    "reduce(fn, [1, 3, 5, 7, 9])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### filter\n",
    "和map()类似，filter()也接收一个函数和一个序列。和map()不同的是，filter()把传入的函数依次作用于每个元素，然后根据返回值是True还是False决定保留还是丢弃该元素"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def is_odd(n):\n",
    "    return n % 2 == 1\n",
    "\n",
    "list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 匿名函数\n",
    "\n",
    "当我们在传入函数时，有些时候，不需要显式地定义函数，直接传入匿名函数更方便。\n",
    "\n",
    "关键字lambda表示匿名函数，冒号前面的x表示函数参数。\n",
    "\n",
    "匿名函数有个限制，就是只能有一个表达式，不用写return，返回值就是该表达式的结果。\n",
    "\n",
    "用匿名函数有个好处，因为函数没有名字，不必担心函数名冲突。此外，匿名函数也是一个函数对象，也可以把匿名函数赋值给一个变量，再利用变量来调用该函数："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 例子\n",
    "list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 相同功能\n",
    "def f(x):\n",
    "    return x * x\n",
    "\n",
    "list(map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 偏函数\n",
    "\n",
    "Python的functools模块提供了很多有用的功能，其中一个就是偏函数（Partial function）。要注意，这里的偏函数和数学意义上的偏函数不一样。\n",
    "\n",
    "在介绍函数参数的时候，我们讲到，通过设定参数的默认值，可以降低函数调用的难度。而偏函数也可以做到这一点。举例如下：\n",
    "\n",
    "int()函数可以把字符串转换为整数，当仅传入字符串时，int()函数默认按十进制转换："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "int('12345')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 但int()函数还提供额外的base参数，默认值为10。如果传入base参数，就可以做N进制的转换：\n",
    "print(int('127', base=8))\n",
    "print(int('127', base=16))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "functools.partial就是帮助我们创建一个偏函数的，不需要我们自己定义int2()，可以直接使用下面的代码创建一个新的函数int2："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import functools\n",
    "int2 = functools.partial(int, base=2)\n",
    "int2('1000000')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 同等效果\n",
    "def int2_b(x, base=2):\n",
    "    return int(x, base)\n",
    "\n",
    "int2_b('1000000')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import math"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "int2 = functools.partial(math.pow, base=2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 练习：\n",
    "# 实现求解2的n次方的各位数之和  如：2**10 = 1024 ，结果为 7\n",
    "# 提示：sum([1,0,2,4]) = 7"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
